﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetaRepository.cs" company="Data Ductus Malmö">
//   (c) 2010 - 2012 Data Ductus Malmö AB
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace DataDuctus.Snmp.Persistor.Core.MetaData
{
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Linq;
  using System.Reflection;

  using DataDuctus.Snmp.Persistor.Core.Attributes;
  using DataDuctus.Snmp.Persistor.Core.MetaData.Extensions;

  using Lextm.SharpSnmpLib;

  /// <summary>
  /// The meta repository.
  /// </summary>
  public class MetaRepository
  {
    #region Constructors and Destructors

    /// <summary>
    /// Initializes a new instance of the <see cref="MetaRepository"/> class.
    /// </summary>
    internal MetaRepository()
    {
      this.Classes = new Dictionary<Type, ClassMeta>();
    }

    #endregion

    #region Public Properties

    /// <summary>
    /// Gets the classes.
    /// </summary>
    public IDictionary<Type, ClassMeta> Classes { get; private set; }

    #endregion

    #region Methods

    /// <summary>
    /// The add.
    /// </summary>
    /// <param name="type">
    /// The type.
    /// </param>
    internal void Add(Type type)
    {
      if (this.Classes.ContainsKey(type))
      {
        return;
      }

      var attrs = new[] { typeof(IndexAttribute), typeof(PropertyAttribute) };

      ClassMeta clzMeta = ToClassMeta(type);

      IEnumerable<PropertyMeta> props = from property in type.GetProperties()
                                        where property.HasAttributes(attrs, true)
                                        select ToPropertyMeta(property, property.GetAttributes(attrs, true));

      clzMeta.Properties = (from property in props orderby property.SubOid select property).ToList();

      clzMeta.Indexes = (from prop in clzMeta.Properties where prop.IsIndex orderby prop.SubOid select prop).ToArray();
      clzMeta.IsTable = 0 != clzMeta.Indexes.Length;

      clzMeta.Accessors = (from property in type.GetProperties()
                           where property.HasAttribute<SnmpAccessorAttribute>(true)
                           select ToAccessorMeta(property)).ToList();

      clzMeta.HasAccessors = 0 != clzMeta.Accessors.Count;

      this.Classes.Add(type, clzMeta);
    }

    /// <summary>
    /// The get accessor base oid.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// The Lextm.SharpSnmpLib.ObjectIdentifier.
    /// </returns>
    private static ObjectIdentifier GetAccessorBaseOid(PropertyInfo property)
    {
      Type type = GetSnmpType(property);

      SnmpAccessorAttribute oid = type.GetAttributes<SnmpAccessorAttribute>(true).FirstOrDefault();

      if (null != oid)
      {
        return new ObjectIdentifier(oid.Oid);
      }

      ClassMeta clzMeta = ToClassMeta(type);

      return new ObjectIdentifier(clzMeta.Oid);
    }

    /// <summary>
    /// The get snmp type.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// The System.Type.
    /// </returns>
    private static Type GetSnmpType(PropertyInfo property)
    {
      if (!IsEnumerable(property))
      {
        return property.PropertyType;
      }

      Type[] genericargs = property.PropertyType.GetGenericArguments();

      Type valueType = 0 != genericargs.Length ? genericargs[0] : property.PropertyType.GetElementType();

      return valueType;
    }

    /// <summary>
    /// The is enumerable.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// The System.Boolean.
    /// </returns>
    private static bool IsEnumerable(PropertyInfo property)
    {
      return null != property.PropertyType.GetInterface("System.Collections.IEnumerable");
    }

    /// <summary>
    /// The to accessor meta.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// The SnmpPersistor.Core.MetaData.AccessorMeta.
    /// </returns>
    private static AccessorMeta ToAccessorMeta(PropertyInfo property)
    {
      return new AccessorMeta
        {
          Accessor = property, 
          IsTable = IsEnumerable(property), 
          EntityType = GetSnmpType(property), 
          Oid = GetAccessorBaseOid(property)
        };
    }

    /// <summary>
    /// The to class meta.
    /// </summary>
    /// <param name="t">
    /// The t.
    /// </param>
    /// <returns>
    /// The SnmpPersistor.Core.MetaData.ClassMeta.
    /// </returns>
    private static ClassMeta ToClassMeta(Type t)
    {
      EntityAttribute entityAttribute = (from attr in t.GetAttributes<EntityAttribute>(true) select attr).First();

      return new ClassMeta
        {
           Oid = entityAttribute.BaseOid, Description = entityAttribute.Description, ReflectedType = t 
        };
    }

    /// <summary>
    /// The to property meta.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <param name="attributes">
    /// The attributes.
    /// </param>
    /// <returns>
    /// <see cref="PropertyMeta"/>.
    /// </returns>
    private static PropertyMeta ToPropertyMeta(PropertyInfo property, IEnumerable<Attribute> attributes)
    {
      var propertyMeta = new PropertyMeta();

      if (null == attributes)
      {
        return propertyMeta;
      }

      var propAttr =
        (PropertyAttribute)
        (from attr in attributes where attr.GetType() == typeof(PropertyAttribute) select attr).FirstOrDefault();

      propertyMeta.Description = propAttr.Description;
      propertyMeta.MaxLength = propAttr.MaxLength;
      propertyMeta.SubOid = propAttr.SubOid;
      propertyMeta.Access = propAttr.Access;
      propertyMeta.Property = property;

      propertyMeta.Converter = null == propAttr.Type
                                 ? null
                                 : (TypeConverter)propAttr.Type.GetConstructor(Type.EmptyTypes).Invoke(null);

      var idxAttr =
        (IndexAttribute)
        (from attr in attributes where attr.GetType() == typeof(IndexAttribute) select attr).FirstOrDefault();

      if (null != idxAttr)
      {
        propertyMeta.IsIndex = true;
      }

      return propertyMeta;
    }

    #endregion
  }
}